//
// Created by sanfen on 2021/3/5.
//

#include <iostream>
#include "chip8.h"

#ifdef __linux__
void Beep(int val1,int val2)();
#endif
#define unknown_opcode(op) \
    do { \
        fprintf(stderr, "Unknown opcode: 0x%x\n", op); \
        fprintf(stderr, "kk: 0x%02x\n", kk); \
        fprintf(stderr, "System Shutdown!!!\n"); \
        exit(42); \
    } while (0)


//# define DEBUG
//#ifdef DEBUG
//#define p(...) printf(__VA_ARGS)
//#else
//#define p(...)
//#endif

#define FONTSET_ADDRESS 0x00
#define FONTSET_BYTES_PER_CHAR 5
//字节大小
unsigned char chip8_fontset[80]={
        0xF0,0x90,0x90,0x90,0xF0,//0
        0x20,0x60,0x20,0x20,0x70,//1
        0xF0,0x10,0xF0,0x80,0xF0,//2
        0xF0,0x10,0xF0,0x10,0xF0,//3
        0x90,0x90,0xF0,0x10,0x10,//4
        0xF0,0x80,0xF0,0x10,0xF0,//5
        0xF0,0x80,0xF0,0x90,0xF0,//6
        0xF0,0x10,0x20,0x40,0x40,//7
        0xF0,0x90,0xF0,0x90,0xF0,//8
        0xF0,0x90,0xF0,0x10,0xF0,//9
        0xF0,0x90,0xF0,0x90,0x90,//A
        0xE0,0x90,0xE0,0x90,0xE0,//B
        0xF0,0x80,0x80,0x80,0xF0,//C
        0xE0,0x90,0x90,0x90,0xE0,//D
        0xF0,0x80,0xF0,0x80,0xF0,//E
        0xF0,0x80,0xF0,0x80,0x80,//F

};
static inline uint8_t randbyte(){return (rand()%256);}



void CHIP8::initialize(){
    //TODO：初始化内存与寄存器（只执行一次）
    int i;

    PC=0x200;
    opcode=0;
    I=0;
    SP=0;

    memset(memory,0,sizeof(uint8_t)*MEM_SIZE);
    memset(V,0,sizeof(uint8_t)*16);
    memset(gfx,0,sizeof(uint8_t)*GFX_SIZE);
    memset(stack,0,sizeof (uint16_t)*STACK_SIZE);
    memset(key,0,sizeof(uint8_t)*KEY_SIZE);

    for(int i=0;i<80;i++){
        memory[FONTSET_ADDRESS+i]=chip8_fontset[i];
    }

    draw_flag=true;
    delay_timer=0;
    sound_timer=0;
    srand(time(NULL));
}



void CHIP8::runCycle() {
    int i;
    uint8_t x, y, n;
    uint8_t kk;
    uint16_t nnn;
    /*TODO: 获取opcode
     * pc寄存器代表当前执行的汇编代码，内存里的当前汇编代码左移8位和pc寄存器的下一条指令取或就是当前操作数
     */
    //TODO:获取opcode
    opcode = memory[PC] << 8 | memory[PC + 1];
    //获取高四位和低四位
    x = (opcode >> 8) & 0x000F; // 取低4位数据
    y = (opcode >> 4) & 0x000F; // 取高4位数据
    //获取操作码
    n = opcode & 0x000F; // 取四位汇编操作码
    kk = opcode & 0x00FF; // 取四位汇编寄存器码
    nnn = opcode & 0x0FFF; // 取数据码
    std::cout<<"PC:"<<PC<<"\nOp:"<<opcode<<std::endl;
    printf("PC: 0x%04x Op: 0x%04x  kk: 0x%04x\n", PC, opcode,kk);


//CPU指令设置，共25个指令
    switch (opcode & 0xF000) {
        case 0x0000:
            switch (kk) {
                //清屏
                case 0x00E0:
                    printf("Clear the screen!\n");
                    memset(gfx, 0, sizeof(uint8_t) * GFX_SIZE);
                    draw_flag = true;
                    PC += 2;
                    break;
                    //从子程序返回
                case 0x00EE:
                    printf("ret\n");
                    PC = stack[--SP];
                    break;
                default:
                    unknown_opcode(opcode);
            }
            break;
            //跳转到地址NNN
        case 0x1000:
            printf("Jump to address 0x%x\n", nnn);
            PC = nnn;
            break;
            //调用子程序NNN
        case 0x2000:
            printf("Call address 0x%x\n", nnn);
            stack[SP++] = PC + 2;
            PC = nnn;
            break;
            //如果VX等于NN则跳过下一条指令（通常时跳过一条跳转指令）
        case 0x3000:
            printf("Skip next instruction if 0x%x==0x%x\n", V[x], kk);
            PC += (V[x] == kk) ? 4 : 2;
            break;
            //如果VX不等于NN则跳过下一条指令（同上）
        case 0x4000:
            printf("Skip next instruction if 0x%x!=0x%0x\n", V[x], kk);
            PC += (V[x] != kk) ? 4 : 2;
            break;
            //如果VX的值等于VY则跳过下一条指令（同上）
        case 0x5000:
            printf("Skip next instruction if 0x%x!=0x%0x\n", V[x], V[y]);
            PC += (V[x] == V[y]) ? 4 : 2;
            break;
            //VX = NN
        case 0x6000:
            printf("Set V[0x%x] to 0x%x\n", x, kk);
            V[x] = kk;
            PC += 2;
            break;
            //VX += NN，进位标记不会改变
        case 0x7000:
            printf("Set V[0x%d] to V[0x%d]+0x%x\n", x, x, kk);
            V[x] += kk;
            PC += 2;
            break;
        case 0x8000:
            switch (n) {
                //VX = VY
                case 0x0:
                    printf("V[0x%x]=V[%x%x]=0x%x\n", x, y, V[y]);
                    V[x] = V[y];
                    break;
                    //VX | VX
                case 0x1:
                    printf("V[0x%x]=V[%x%x]=0x%x\n", x, y, V[y]);
                    V[x] = V[x] | V[y];
                    break;
                    //VX & VX
                case 0x2:
                    printf("V[0x%x] &=V[%x%x]=0x%x\n", x, y, V[y]);
                    V[x] = V[x] & V[y];
                    break;
                    //VX ^ VX
                case 0x3:
                    printf("V[0x%x]^=V[%x%x]=0x%x\n", x, y, V[y]);
                    V[x] = V[x] ^ V[y];
                    break;
                    //VX += VY，VX有进位（大于255）时VF为1，否则VF为0
                case 0x4:
                    printf("V[0x%x]=V[%x%x]+V[%x%x]=0x%x+0x%x\n", x, x, y, V[x], V[y]);
                    V[0xF] = ((int) V[x] + (int) V[y] > 255 ? 1 : 0);
                    V[x] = V[x] + V[y];
                    break;
                    //VX -= VY，VX负数时VF为0，否则VF为1
                case 0x5:
                    printf("V[0x%x]=V[%x%x]-V[%x%x]=0x%x-0x%x\n", x, x, y, V[x], V[y]);
                    V[0xF] = V[x] > V[y] ? 1 : 0;
                    V[x] = V[x] - V[y];
                    break;
                    //VX >>= 1，VX最低位存入VF中
                case 0x6:
                    printf("V[0x%x]=V[0x%x]>>1=0x%x>>1\n", x, x, V[x]);
                    V[0xF] = V[x] & 0x1;
                    V[x] = (V[x] >> 1);
                    break;
                    //VX = VY - VX，VX负数时VF为0，否则VF为1
                case 0x7:
                    printf("V[0x%x]=V[%x%x]-V[%x%x]=0x%x-0x%x\n", x, y, x, V[y], V[x]);
                    V[0xF] = V[y] > V[x] ? 1 : 0;
                    V[x] = V[x] - V[y];
                    break;
                    //VX <<= 1，VX最高位存入VF中
                case 0xE:
                    printf("V[0x%x]=V[0x%x]<<1=0x%x<<1\n", x, x, V[x]);
                    V[0xF] = (V[x] > 7) & 0x1;
                    V[x] = (V[x] << 1);
                    break;
                default:
                    unknown_opcode(opcode);
            }
            PC += 2;
            break;
            //如果VX不等于VY则跳过下一条指令（通常时跳过一条跳转指令）
        case 0x9000:
            switch (n) {
                case 0x0:
                    printf("Skip next instruction if 0x%x!=0x%x\n", V[x], V[y]);
                    PC += (V[x != V[y]]) ? 4 : 2;
                    break;
                default:
                    unknown_opcode(opcode);

            }
            break;
            //将I设置为NNN
        case 0xA000:
            printf("Set I to 0x%x\n", nnn);
            I = nnn;
            PC += 2;
            break;
            //跳转到地址V0+NNN，PC=V0+NNN
        case 0xB000:
            printf("Jump to 0x%x+V[0] (0x%x)\n", nnn, V[0]);
            PC = nnn + V[0];
            break;
            //随机数 VX = rand() & NN
        case 0xC000:
            printf("V[0x%x]=random byte\n", x);
            V[x] = randbyte() && kk;
            PC += 2;
            break;
            //在(VX,VY)绘制一个宽8像素、高N像素的精灵。每8个像素从I寄存器中的地址逐步读出一字节（8位），
            // 在执行该语句时I内的值不可以改变，如果像素反转为0则碰撞检测将VF置为1
        case 0xD000:
            printf("Draw sprite at (V[0x%x],V[0x%x])=(0x%x,0x%x) of height %d", x, y, V[x], V[y], n);
            draw_sprite(V[x], V[y], n);
            PC += 2;
            draw_flag = true;
            break;
        case 0xE000:
            //按键事件处理
            switch (kk) {
                //跳过下一条指令，如果存储在VX的键值所应的键被按下
                case 0x9E:
                    printf("Skip next instruction if key[%d] is pressed\n", x);
                    PC += (key[V[x]]) ? 4 : 2;
                    break;
                    //跳过下一条指令，如果存储在VX的键值所应的键没有被按下
                case 0xA1:
                    printf("Skip next instruction if key[%d] is pressed\n", x);
                    PC += (key[V[x]]) ? 4 : 2;
                    break;
                default:
                    unknown_opcode(opcode);
            }
            break;
            //misc
        case 0xF000:
            switch (kk) {
                //获取延时寄存器的值
                case 0x07:
                    printf("V[0x%x]=delay timer=%d\n", x, delay_timer);
                    V[x] = delay_timer;
                    PC += 2;
                    break;
                    //等待按键，将按键的值存入VX（阻塞指令，所有指令将等待该指令执行完
                case 0x0A: {
                    int i = 0;
                    printf("Wait for key instruction\n");
                    while (true) {
                        for (i = 0; i < KEY_SIZE; i++) {
                            if (key[i]) {
                                V[x] = i;
                                goto got_key_press;
                            }
                        }
                    }
                    got_key_press:
                    PC += 2;
                    break;
                }
                    //将VX的值存入延时寄存器
                case 0x15:
                    printf("delay timer=V[0x%x]=%d\n", x, V[x]);
                    delay_timer = V[x];
                    PC += 2;
                    break;
                    //将VX的值存入声音寄存器
                case 0x18:
                    printf("sound timer=V[0x%x]=%d\n", x, V[x]);
                    sound_timer = V[x];
                    PC += 2;
                    break;
                    //I += VX
                case 0x1E:
                    printf("I=I+V[0x%x]=0x%x+0x%x\n", x, I, V[x]);
                    V[0xF] = (I + V[x] > 0xfff) ? 1 : 0;
                    I = I + V[x];
                    PC += 2;
                    break;
                    //将VX中的精灵地址赋值给I，字符0-F由4X5字体表示
                case 0x29:
                    printf("I = location of font for character V[0x%x] = 0x%x\n", x, V[x]);
                    I = FONTSET_BYTES_PER_CHAR;
                    PC += 2;
                    break;
                    //将VX中值的BCD码存入I中的地址内，百位在I，十位在I+1，个位在I+2
                case 0x33:
                    printf("Store BCD for %d starting at address 0x%x\n", V[x], I);
                    memory[I] = (V[x] % 1000) / 100;//百位
                    memory[I + 1] = (V[x] % 100) / 10;//十位
                    memory[I + 2] = (V[x] % 10);//个位
                    PC += 2;
                    break;
                    //将V0到VX的值存入I中地址为起始的内存空间
                case 0x55:
                    printf("Cope sprite from registers 0 to 0x%x into memory at address 0x%x\n", x, I);
                    for (int i = 0; i <= x; i++) {
                        memory[I + i] = V[i];
                    }
                    I += x + 1;
                    PC += 2;
                    break;
                    //将I中地址为起始的内容依次存入V0-VX
                case 0x65:
                    printf("Cope sprite from  memory at 0x%x into registers 0 at address 0x%x\n", x, I);
                    for (int i = 0; i <= x; i++) {
                        V[i] = memory[I + i];
                    }
                    I += x + 1;
                    PC += 2;
                    break;
                default:
                    unknown_opcode(opcode);
            }
            break;
        default:
            unknown_opcode(opcode);

    }
}

    void CHIP8::tick() {
        //更新计时器
        if (delay_timer > 0) {
            --delay_timer;
        }
        if (sound_timer > 0) {
            --sound_timer;
            if (sound_timer == 0) {
                Beep(400, 400);
#ifdef DEBUG
                printf("BEEP\n");
#endif
            }
        }
    }
    void CHIP8::draw_sprite(uint8_t x, uint8_t y, uint8_t n) {
        unsigned row = y, col = x;
        unsigned byte_index;
        unsigned bit_index;

        //中断寄存器置0
        V[0xF] = 0;
        for (byte_index = 0; byte_index < n; byte_index++) {
            uint8_t byte = memory[I + byte_index];
            for (bit_index = 0; bit_index < 8; bit_index++) {
                uint8_t bit = (byte >> bit_index) & 0x1;
                uint8_t *pixelp = &gfx[(row + byte_index) % GFX_ROWS][(col + (7 - bit_index)) % GFX_COLS];
                if (bit == 1 && *pixelp == 1) V[0xF] = 1;
                *pixelp = *pixelp ^ bit;
            }
        }
    }

    void CHIP8::loadGame(char *file_path) {
        FILE *fgame;

        fgame = fopen(file_path, "rb");

        if (NULL == fgame) {
            fprintf(stderr, "Unable to open game: %s\n", file_path);
            exit(42);
        }

        fread(&memory[0x200], 1, MAX_GAME_SIZE, fgame);

        fclose(fgame);
    }


    void CHIP8::printState() {
        printf("-----------------------------------------------------------\n");
        printf("\n");
        printf("V0:0x%02x  V4:0x%02x  V8:0x%02x VC:0x%02x\n",V[0],V[4],V[8],V[12]);
        printf("V1:0x%02x  V5:0x%02x  V9:0x%02x VD:0x%02x\n",V[1],V[5],V[9],V[13]);
        printf("V2:0x%02x  V6:0x%02x  VA:0x%02x VE:0x%02x\n",V[2],V[6],V[10],V[14]);
        printf("V3:0x%02x  V7:0x%02x  VB:0x%02x VF:0x%02x\n",V[3],V[7],V[11],V[15]);
        printf("\n");
        printf("PC: 0x%04x\n",PC);
        printf("\n");
        printf("\n");
}

void CHIP8::debug_screen() {
    int x,y;
    for(y=0;y<GFX_ROWS;y++){
        for(x=0;x<GFX_COLS;x++){
            if(gfx[y][x]==0) printf("0");
            else printf(" ");
        }
        printf("\n");
    }
}

void CHIP8::print_cpu_info() {
    std::cout << ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" << std::endl;
    std::cout << "CUP name          |   " <<this->name << std::endl;
    std::cout << "Screen rows       |   " <<this->screen_rows << std::endl;
    std::cout << "Screen cols       |   " <<this->screen_cols << std::endl;
    std::cout << "Clock rate_ms     |   " <<this->clock_rate_ms << std::endl;
    std::cout << "Clock hz          |   " <<this->clock_hz << std::endl;
    std::cout << "Pixel size        |   " <<this->pixel_size << std::endl;
    std::cout << ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" << std::endl;
}

void CHIP8::setkeys(int index, int state) {
    this->key[index]=state;
}




